﻿using System;
using System.Collections;
using System.Reflection;
using CI.Interfaces;
using System.Runtime.Serialization;
using CI.Utils;

namespace CI.Organism
{
    /// <summary>
    /// abstract class for synapse
    /// </summary>
    [DataContract(IsReference = true)]
    public abstract class Synapse : ISynapse
    {
        #region Properties and fields

        /// <summary>
        /// Input value from sensory or other neuron
        /// </summary>
        public long InputValue { get; set; }

        /// <summary>
        /// Input cycles wait until input value can be used
        /// This value gets decremented each check
        /// Simulates the lenght of a axiom and the delay associated with it
        /// </summary>
        [DataMember]
        public long InputWait { get; set; }

        /// <summary>
        /// Time to hold the the potential, eg how many cycles to maintain the value,
        /// this is for every time the synapse has a potential
        /// </summary>
        [DataMember]
        public long OutputHold { get; set; }

        /// <summary>
        /// The internal output hold counters
        /// </summary>
        protected Int64 OutputHoldCount = 0;

        /// <summary>
        /// Weight(Bias), is this going to veto eg negative number or permit eg positive number
        /// </summary>
        public double InputWeight
        {
            get
            {
                // if we havent been set yet
                if (!_inputBiasSet)
                {
                    _inputBias = InitInputWeight;
                    _inputBiasSet = true;
                }
                return _inputBias;
            }
            set { _inputBias = value; }
        }
        private double _inputBias = 0;
        private bool _inputBiasSet = false;

        /// <summary>
        /// Init Input Weight at synapse creation
        /// </summary>
        [DataMember]
        public double InitInputWeight { get; set; }

        /// <summary>
        /// Output value read by the neuron
        /// </summary>
        public Int64 OutputValue
        {
            get
            {
                // save the output value
                return _outputValue;
            }
        }

        /// <summary>
        /// Output value read by the neuron
        /// </summary>
        protected Int64 _outputValue = 0;

        protected Int64 _lastEvalOutputValue = 0;

        /// <summary>
        /// Learning rule for the synapse
        /// </summary>
        [DataMember]
        public ILearningRule LearningRule { get; set; }

        /// <summary>Synapses guid</summary>
        [DataMember]
        public Guid Guid { get; protected set; }

        /// <summary>Neural system it belongs to</summary>
        [DataMember]
        protected INeuralSystem NeuralSystem;

        /// <summary>Last HeartBeat evaluation count for the Organism</summary>
        public uint? LastEvalHeartbeatCount { get; protected set; }

        /// <summary>Last evaluation result to be returned if the eval gets called twice</summary>
        protected bool _lastEvalResult;

        /// <summary>
        /// Target connected neurons/synapseIO
        /// </summary>        
        public ISynapticConnection TargetNotification
        {
            get { return _targetNotification; }
        }
        [DataMember]
        internal ISynapticConnection _targetNotification;



        /// <summary>
        /// Source connected neurons/synapseIO
        /// </summary>
        public ISynapticConnection SourceNotification
        {
            get { return _sourceNotification; }
        }
        [DataMember]
        internal ISynapticConnection _sourceNotification;

        /// <summary>
        /// notify once evaluation has been completed
        /// </summary>
        public event Action EvaluationComplete;


        #endregion

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            EvaluationComplete = null;
        }

        public void RaiseEvaluationComplete()
        {
            if (EvaluationComplete != null)
                EvaluationComplete();
        }

        public Synapse()
        {
            OutputHold = 0;
            InputWait = 0;
            InputValue = 0;
        }

        /// <summary>
        /// Create a new synapse, this will bind it target and source neurons on creation and add them to the input and output lists on the neuron
        /// </summary>
        /// <param name="mutationRate">mutation rate</param>
        /// <param name="childOrganism">child organism the learning rule will end up in</param>
        /// <returns>new cell</returns>
        public static ISynapse CreateNewSynapse(double mutationRate, IOrganism childOrganism)
        {
            #region Argument Checks
            if (childOrganism == null)
                throw new ArgumentException("childOrganism");
            if (childOrganism.NeuralSystem == null)
                throw new ArgumentException("childOrganism has no Neural System");
            if ((childOrganism.Cells == null) || (childOrganism.Cells.Count <= 1))
                throw new ArgumentException("childOrganism has not enough Neural Cells");
            #endregion

            // mutate adding new cells
            // we need to reflect and see what genes are available
            var synapses = Globals.GetConcreteClasses(typeof(ISynapse));

            // save its type
            var classType = (Type)synapses[Mutation.CollectionSelect(synapses)];
            // We are anticipating a constructor that takes 1 parameters,
            var conTypes = new Type[2];
            conTypes[0] = typeof(Guid);
            conTypes[1] = typeof(INeuralSystem);

            // Get the constructor that adheres to our parameters.
            ConstructorInfo constructor = classType.GetConstructor(conTypes);
            // Define the parameters to pass into our constructor when we call it.
            var constructorParams = new object[2];
            constructorParams[0] = Guid.NewGuid();
            constructorParams[1] = childOrganism.NeuralSystem;
            SimEnv.Instance.TraceLogger.LogDebug(string.Format("Invoking constructor of type {0}", classType));

            // Invoke the constructor dynamically, getting an instance of the class.  
            var synapse = constructor.Invoke(constructorParams) as ISynapse;

            MutateSourceAndTargetSynapticConnections(childOrganism, mutationRate, synapse);

            // mutate the learning rule
            synapse = synapse.Mutate(mutationRate, childOrganism);

            return synapse;
        }

        public static void MutateSourceAndTargetSynapticConnections(IOrganism childOrganism, double mutationRate, ISynapse synapse)
        {
            if (Mutation.Mutate5050())
            {
                BindRandomSourceNeuronAndSynapseIO(childOrganism, mutationRate, synapse);
                BindRandomTargetNeuronAndSynapseIO(childOrganism, mutationRate, synapse);
            }
            else
            {
                BindRandomTargetNeuronAndSynapseIO(childOrganism, mutationRate, synapse);
                BindRandomSourceNeuronAndSynapseIO(childOrganism, mutationRate, synapse);
            }
        }

        private static void BindRandomTargetNeuronAndSynapseIO(IOrganism childOrganism, double mutationRate, ISynapse synapse)
        {
            // check against binding name
            if (BindSynapseToSynapseIOInsteadOfNeuron(childOrganism, synapse, synapse.TargetNotification as INeuron))
            {
                synapse.AddSynapticConnectionTarget(GetRandomSynapseIOSynapticConnection(childOrganism, synapse.TargetNotification));
            }
            else
            {
                BindSynapseToRandomNeuron(childOrganism, synapse.TargetNotification as INeuron).OutputSynapse.Add(synapse);
            }
        }

        private static void BindRandomSourceNeuronAndSynapseIO(IOrganism childOrganism, double mutationRate, ISynapse synapse)
        {
            // check against binding name
            if (BindSynapseToSynapseIOInsteadOfNeuron(childOrganism, synapse, synapse.SourceNotification as INeuron))
            {
                synapse.AddSynapticConnectionSource(GetRandomSynapseIOSynapticConnection(childOrganism, synapse.TargetNotification));
            }
            else
            {
                BindSynapseToRandomNeuron(childOrganism, synapse.SourceNotification as INeuron).InputSynapse.Add(synapse);
            }
        }

        protected static ISynapticConnection GetRandomSynapseIOSynapticConnection(IOrganism childOrganism, ISynapticConnection opposingSynapticConnection)
        {
            ISynapticConnection synapticConnection;
            do
            {
                synapticConnection = childOrganism.GetIOManifest()[Mutation.CollectionSelect(childOrganism.GetIOManifest())];
            } while (synapticConnection == opposingSynapticConnection);
            return synapticConnection;
        }

        protected static bool BindSynapseToSynapseIOInsteadOfNeuron(IOrganism childOrganism, ISynapse synapse, INeuron compareNeuron)
        {
            int ioManifestCount = childOrganism.GetIOManifest().Count;
            if (ioManifestCount == 0)
                return false;

            var neurons = childOrganism.Cells.Neurons();
            int neuronCount = neurons.Count;
            if ((neuronCount == 1) && (neurons[0] == compareNeuron))
                return true;

            if (neuronCount == 0)
                return true;

            var selectedIndex = Mutation.RangeSelect(1, ioManifestCount + neuronCount);

            return selectedIndex <= ioManifestCount ? true : false;
        }

        protected static INeuron BindSynapseToRandomNeuron(IOrganism childOrganism, INeuron compareNeuron)
        {
            var neurons = childOrganism.Cells.Neurons();

            // we cant 
            if (((neurons.Count == 1) && (neurons[0] == compareNeuron)) || neurons.Count == 0)
            {
                return null;
            }

            INeuron neuron;
            do
            {
                neuron = neurons[Mutation.CollectionSelect((ICollection)neurons)];
            } while (compareNeuron == neuron);
            return neuron;
        }

        /// <summary>
        /// do a equals comparison of one synapse type to another only comparing the value types, no refference comparisons
        /// will call the check for learning rules
        /// </summary>
        /// <param name="synapse">synapse to compare against</param>
        /// <returns>true if equal value types</returns>
        public virtual bool EqualsValueTypes(ISynapse synapse)
        {
            // if we didnt get one passed in fail
            if (synapse == null)
                return false;
            // check the local synapse
            if (!GetType().Equals(synapse.GetType()))
                return false;
            if (Guid != synapse.Guid)
                return false;
            if (InitInputWeight != synapse.InitInputWeight)
                return false;
            if (InputValue != synapse.InputValue)
                return false;
            if (InputWait != synapse.InputWait)
                return false;
            if (InputWeight != synapse.InputWeight)
                return false;
            if (OutputHold != synapse.OutputHold)
                return false;
            if (OutputValue != synapse.OutputValue)
                return false;

            // check the source neuron GUID
            if ((SourceNotification != null) && (synapse.SourceNotification != null))
            {
                if (SourceNotification.Guid != synapse.SourceNotification.Guid)
                    return false;
            }
            else
            {
                if ((SourceNotification != null) && (synapse.SourceNotification == null))
                    return false;
                if ((SourceNotification == null) && (synapse.SourceNotification != null))
                    return false;
            }

            // check the target neuron GUID
            if ((TargetNotification != null) && (synapse.TargetNotification != null))
            {
                if (TargetNotification.Guid != synapse.TargetNotification.Guid)
                    return false;
            }
            else
            {
                if ((TargetNotification != null) && (synapse.TargetNotification == null))
                    return false;
                if ((TargetNotification == null) && (synapse.TargetNotification != null))
                    return false;
            }

            // check the learning rule
            if (LearningRule != null)
            {
                if (!LearningRule.EqualsValueTypes(synapse.LearningRule))
                    return false;
            }
            else
            {
                // make sure the synapse learning rule is their
                if (synapse.LearningRule != null)
                    return false;
            }

            // all okay so go...
            return true;
        }

        /// <summary>
        /// Fire the synapse, notify it that a potential has arrived and the value
        /// </summary>
        public abstract void FireInput(long inputValue);

        public abstract event Action<long> ActivationEvent;

        /// <summary>
        /// Take this parent synapse and create a new synapse to be put in the new child Organism
        /// Mutate the current synapse and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new synapse for child</returns>
        public abstract ISynapse Mutate(double mutationRate, IOrganism childOrganism);

        /// <summary>
        /// Called each clock tick cycle to calculate and populate the output value and call any delegates
        /// </summary>
        public abstract bool Evaluate(uint neuralHB);

        /// <summary>
        /// Reset values for processing
        /// </summary>
        public void ResetValues()
        {
            _outputValue = 0;
        }


        public void AddSynapticConnectionTarget(ISynapticConnection synapticConnection)
        {
            _targetNotification = synapticConnection;
        }

        public void AddSynapticConnectionSource(ISynapticConnection synapticConnection)
        {
            _sourceNotification = synapticConnection;
        }
    }
}
